package cn.amebius.simpledocker.config;

import cn.amebius.simpledocker.entity.model.DockerClientModel;
import cn.amebius.simpledocker.entity.po.DockerPo;
import cn.amebius.simpledocker.enums.DockerType;
import cn.amebius.simpledocker.exception.DataErrorException;
import cn.amebius.simpledocker.exception.DockerErrorException;
import cn.amebius.simpledocker.service.DockerService;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.model.Info;
import com.github.dockerjava.api.model.Version;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.netty.NettyDockerCmdExecFactory;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.DependsOn;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.ws.rs.PathParam;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

import static java.lang.String.format;

/**
 * DockerClient初始化工厂
 * @author 阿伟
 * @date 2023/7/28 20:22
 */
@Slf4j
@Component
public class DockerFactory{

    @Autowired
    DockerService dockerService;

    @Getter
    private final Map<Integer, DockerClientModel> clientGroup = new HashMap<>(); //DockerClient组

    private ApplicationContext context; //上下文实例

    @Getter
    private Integer alives = 0;

    @Getter
    private Integer deads = 0;

    @Getter
    private Integer taskTimes = 0;


    /**
     * 通过DockerPo开启一个DockerClient，并且返回DockerClientModel用于维护
     * @param dockerPo Docker主机相关连接数据
     * @return dcm对象
     */
    public DockerClientModel startDockerClient(DockerPo dockerPo){
        if (null == dockerPo.getType() || dockerPo.getType().isEmpty()) {
            throw new DataErrorException("数据错误:Docker主机Type数据丢失");
        }

        String url = null;
        if (dockerPo.getType().equals(DockerType.LOCAL.getDesc())) {
            //本地连接
            url = "unix:///var/run/docker.sock";
        }
        if (dockerPo.getType().equals(DockerType.REMOTE.getDesc())) {
            // 远程连接
            url = format("tcp://%s:%s", dockerPo.getHost(), dockerPo.getPort());
        }
        if (dockerPo.getType().equals(DockerType.REMOTE_SSL.getDesc())) {
            //TODO 安全远程
        }
        //无账号密码
        DefaultDockerClientConfig ddcc = DefaultDockerClientConfig
                                         .createDefaultConfigBuilder()
                                         .withDockerHost(url)
                                         .build();
        if (!dockerPo.getUsername().equals("空") && !dockerPo.getPassword().equals("空")) {
            //有账号密码
            ddcc = DefaultDockerClientConfig
                    .createDefaultConfigBuilder()
                    .withDockerHost(url)
                    .withRegistryUsername(dockerPo.getUsername())
                    .withRegistryPassword(dockerPo.getPassword())
                    .build();
        }
        DockerClientModel dcm = new DockerClientModel();
        DockerClient dc;
        dcm.setDocker(dockerPo);
        try {
            NettyDockerCmdExecFactory nettyDockerCmdExecFactory = new NettyDockerCmdExecFactory();
            dc = DockerClientBuilder.getInstance(ddcc)
                    .withDockerCmdExecFactory(nettyDockerCmdExecFactory)
                    .build();
            dcm.setDockerClient(dc);
            dcm.setAlive(true);
            this.updateInfo(dcm);
            alives++;
            log.info("docker主机连接：成功({}:{})",dockerPo.getHost(),dockerPo.getPort());
        } catch (Exception e) {
            log.error("docker主机连接：失败({}:{})",dockerPo.getHost(),dockerPo.getPort());
            dcm.setAlive(false);
            deads++;
        }
        return dcm;
    }

    /**
     * 通过dcm来ping通Docker主机
     * @param dockerClientModel dcm对象
     * @return 是否ping通
     */
    public boolean pingDockerClient(DockerClientModel dockerClientModel) {
        DockerClient dc = dockerClientModel.getDockerClient();
        try {
            dc.pingCmd().exec();
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 通过key来ping通Docker主机
     * @param key Docker主机的Key
     * @return  是否ping通
     */
    public boolean pingDockerClient(Integer key) {
        DockerClientModel dcm = clientGroup.get(key);
        if (dcm == null) {
            return false;
        }
        DockerClient dc = dcm.getDockerClient();
        try {
            dc.pingCmd().exec();
            if (!dcm.getAlive()) {
                deads--;
                alives++;
                dcm.setAlive(true);
                clientGroup.put(key,dcm);
            }
            return true;
        } catch (Exception e) {
            if (dcm.getAlive()) {
                deads++;
                alives--;
                dcm.setAlive(false);
                clientGroup.put(key,dcm);
            }
            return false;
        }
    }


    /**
     * 广播当前dockerFactory的状态
     */
    public void broadcastStatus() {
        log.info("Docker主机连接状态：clientSize = {},alives = {},deads = {}", clientGroup.size(),alives,deads);
    }

    /**
     * 初始化方法，初始化DockerFactory工厂
     */
//    @Override
//    public void run(String... args) {
//        CompletableFuture.runAsync(() -> dockerInitOperation(false));
//    }

    @PostConstruct
    public void init() {
        log.info("=================");
        CompletableFuture.runAsync(() -> dockerInitOperation(false));
    }

    /**
     * 功能描述：docker初始化操作函数
     *
     * @author HeJiaWei
     * @date 2/6/24
     */
    @Async
    public void dockerInitOperation(boolean isCleanStatus) {
        if (isCleanStatus) {
            cleanStatusQuantity();
        }
        final List<DockerPo> allDocker = dockerService.getAllDocker();
        ExecutorService executor = Executors.newFixedThreadPool(Math.min(allDocker.size(), Runtime.getRuntime().availableProcessors()));

        Map<Integer, Future<DockerClientModel>> futures = new ConcurrentHashMap<>();
        for (DockerPo dockerPo : allDocker) {
            futures.put(dockerPo.getId(), executor.submit(() -> this.startDockerClient(dockerPo)));
        }
        for (Map.Entry<Integer, Future<DockerClientModel>> entry : futures.entrySet()) {
            DockerClientModel dcm = null;
            try {
                dcm = entry.getValue().get();
                clientGroup.put(entry.getKey(), dcm);
            } catch (Exception e) {
                log.error("docker初始化：executor异常");
                e.printStackTrace();
            }
        }

        executor.shutdown();
        while (!executor.isTerminated()) {
        }
        log.info("初始化Client内容完成, clientSize:{}, alives:{}, deads:{}", clientGroup.size(), alives, deads);
    }

    /**
     * 功能描述：清空docker的status函数
     *
     * @author HeJiaWei
     * @date 2/6/24
     */
    public void cleanStatusQuantity() {
        this.alives = 0;
        this.deads = 0;
    }

//    @Override
//    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//        this.context = context;
//    }

    /**
     * 通过key获取clientGroup里的docker的dcm对象
     * @param key map里的Key主键
     * @return dcm对象
     */
    public DockerClientModel getByKey(Integer key) {
        DockerClientModel dcm = clientGroup.get(key);
        if (dcm == null) {
            throw new NullPointerException("主机不存在！");
        }
        return dcm;
    }

    /**
     * 向工厂里面添加一台Docker主机
     * @param dockerPo docker主机的Po数据对象
     */
    public void addClient(DockerPo dockerPo) {
        DockerClientModel dcm = startDockerClient(dockerPo);
        this.clientGroup.put(dockerPo.getId(),dcm);
    }

    public void updateClient(DockerPo dp) {
        DockerClientModel dcm = startDockerClient(dp);
        this.clientGroup.put(dp.getId(),dcm);
    }

    public void deleteClient(Integer key) {
        DockerClientModel dcm = this.clientGroup.get(key);
        DockerClient dc = dcm.getDockerClient();
        if (dcm.getAlive()) {
            if (this.alives != 0) {
                this.alives --;
            }
        } else {
            if (this.deads != 0) {
                this.deads --;
            }
        }
        try {
            dc.close();
        } catch (Exception e) {
            throw new DockerErrorException("Docker主机异常：关闭Client连接出错");
        }
        this.clientGroup.remove(key);
    }

    public void setAlives(Integer alives) {
        this.alives = alives;
    }

    public void setDeads(Integer deads) {
        this.deads = deads;
    }

    public void setTaskTimes(Integer taskTimes) {
        this.taskTimes = taskTimes;
    }

    public void updateInfo(DockerClientModel dcm) {
        DockerClient dc = dcm.getDockerClient();
        Info info = dc.infoCmd().exec();
        Version version = dc.versionCmd().exec();
        dcm.setInfoAndVersion(info,version);
    }

}
